package com.example.demo.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.log.StaticLog;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.config.ConstantConfig;
import com.example.demo.entity.Menu;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import com.example.demo.entity.UserRole;
import com.example.demo.entity.dto.GiveRolesDto;
import com.example.demo.entity.dto.LoginDto;
import com.example.demo.entity.dto.PageDto;
import com.example.demo.entity.vo.LoginVo;
import com.example.demo.entity.vo.PageVo;
import com.example.demo.entity.vo.PicCaptchaVo;
import com.example.demo.enums.StatusCodeEnum;
import com.example.demo.exception.CustomException;
import com.example.demo.service.UserRoleService;
import com.example.demo.service.UserService;
import com.example.demo.mapper.UserMapper;
import com.example.demo.util.AesUtil;
import com.example.demo.util.AuthContextUtil;
import com.example.demo.util.NormalUtil;
import com.example.demo.util.QueryWraperUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
* @author lw
* @description 针对表【user(用户表)】的数据库操作Service实现
* @createDate 2023-12-26 15:32:21
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private UserRoleService userRoleService;


    @Override
    public User getUserByName(String name) {
        return baseMapper.getUserByName(name);
    }

    @Override
    public PicCaptchaVo getPicCaptcha() {
        LineCaptcha lineCaptcha=CaptchaUtil.createLineCaptcha(150,32,4,30);
        String codeKey=IdUtil.simpleUUID();
        String codeValue=lineCaptcha.getCode();
        String base64=lineCaptcha.getImageBase64Data();
        ValueOperations<String,String> redisOps = redisTemplate.opsForValue();
        redisOps.set(ConstantConfig.redis_login_PicCaptcha_prefix + codeKey, codeValue,ConstantConfig.captcha_expire,ConstantConfig.captcha_expire_unit);
        PicCaptchaVo picCaptchaVo=new PicCaptchaVo(AesUtil.encrypt(codeKey),codeValue,base64);
//        picCaptchaVo.setRealCodeKey(codeKey);
        return picCaptchaVo;
    }

    @Override
    public LoginVo loginByAccount(LoginDto loginDto, HttpServletRequest request) {

        if(StrUtil.isEmpty(loginDto.getUsername())) throw new CustomException(StatusCodeEnum.username_null);
        if(StrUtil.isEmpty(loginDto.getPassword())) throw new CustomException(StatusCodeEnum.password_null);
        if(StrUtil.isEmpty(loginDto.getCodeKey())) throw new CustomException(StatusCodeEnum.codeKey_null);
        if(StrUtil.isEmpty(loginDto.getCodeValue())) throw new CustomException(StatusCodeEnum.codeValue_null);

        ValueOperations<String,String> redisOps = redisTemplate.opsForValue();
        String codeValue=redisOps.get(ConstantConfig.redis_login_PicCaptcha_prefix+loginDto.getCodeKey());
        if(StrUtil.isEmpty(codeValue)) throw new CustomException(StatusCodeEnum.codeValue_no_exist);
        else {   // 使用一次立马失效，可以防止抓包人使用账户发送请求获取token，因为图形验证码codeKey是加密的，除非抓包人知道密钥
           redisTemplate.delete(ConstantConfig.redis_login_PicCaptcha_prefix+loginDto.getCodeKey());
        }
        if(!loginDto.getCodeValue().equals(codeValue)) throw new CustomException(StatusCodeEnum.codeValue_error);
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",loginDto.getUsername());
        User user = baseMapper.selectOne(queryWrapper);
        if(user == null) throw new CustomException(StatusCodeEnum.username_error);
        if(!user.getPassword().equals(loginDto.getPassword()))  throw new CustomException(StatusCodeEnum.password_error);
        return new LoginVo(createToken(user,request),user.getId().toString()+"666",null);
    }

    @Override
    public Boolean getShortMessageCaptcha(String phone) {
        // 如果该接口也用于注册，校验用户存在性则不合理
//        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
//        queryWrapper.eq("phone",phone);
//        User user = baseMapper.selectOne(queryWrapper);
//        if(user==null) throw new CustomException(StatusCodeEnum.phone_no_exist);

            ValueOperations<String,String> redisOps = redisTemplate.opsForValue();
            if(redisOps.get(ConstantConfig.redis_login_shortMessageCaptcha_prefix + phone) !=null) 
            throw new CustomException(StatusCodeEnum.shortMessageCaptcha_hasSend);

        //短信服务市场地址：https://market.aliyun.com/apimarket/detail/cmapi00037170?spm=5176.730005.result.6.843a3524iZVj93
        String host = "https://dfsns.market.alicloudapi.com";
        String path = "/data/send_sms";
        String appcode = "你自己的AppCode";
        Map<String, String> headers = new HashMap<>();
        //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
        headers.put("Authorization", "APPCODE " + appcode);
        //根据API的要求，定义相对应的Content-Type
        headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        Map<String, Object> body = new HashMap<>();
        String code= NormalUtil.getRandom(6);
        body.put("content", "code:"+code);
        body.put("template_id", "CST_ptdie100");  //该模板为调试接口专用，短信下发有受限制，调试成功后请联系客服报备专属模板
        body.put("phone_number", phone);
        try{
            HttpResponse httpResponse=HttpRequest.post(host+path).headerMap(headers,true).form(body).execute();
            int res_status=httpResponse.getStatus();
            String res_body=httpResponse.body();
           
            System.out.println(phone+"——"+res_status + "——"+res_body);
            if(res_status>=200 && res_status<300) {
                /* 成功响应示例
                 {
                    "request_id": "TID8bf47ab6eda64476973cc5f5b6ebf57e",
                    "status": "OK"
                  }
                */
                JSONObject resBodyJson = JSON.parseObject(res_body);
                 redisOps.set(ConstantConfig.redis_login_shortMessageCaptcha_prefix + phone, code,ConstantConfig.captcha_expire,ConstantConfig.captcha_expire_unit);
                return true;
            }else {
                return false;
            }
        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public LoginVo loginByPhone(LoginDto loginDto,String openid,HttpServletRequest request) {
        if(StrUtil.isEmpty(loginDto.getShortMessageCode()))  throw new CustomException(StatusCodeEnum.shortMessageCaptcha_null);
        if(!Validator.isMobile(loginDto.getPhone())) throw new CustomException(StatusCodeEnum.phone_error);

        ValueOperations<String,String> redisOps = redisTemplate.opsForValue();
        String shortMessageCaptcha=redisOps.get(ConstantConfig.redis_login_shortMessageCaptcha_prefix+loginDto.getPhone());
        if(StrUtil.isEmpty(shortMessageCaptcha)) throw new CustomException(StatusCodeEnum.shortMessageCaptcha_no_exist);
        if(!loginDto.getShortMessageCode().equals(shortMessageCaptcha)) throw new CustomException(StatusCodeEnum.shortMessageCaptcha_error);
        redisTemplate.delete(ConstantConfig.redis_login_shortMessageCaptcha_prefix+loginDto.getPhone());
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("phone",loginDto.getPhone());
        User user = baseMapper.selectOne(queryWrapper);
        if(user==null) throw new CustomException(StatusCodeEnum.phone_no_exist);

        if(openid!=null){
            user.setWxOpenid(openid);
            baseMapper.updateById(user);
        }

        return new LoginVo(createToken(user,request),user.getId().toString()+"666",null);
    }

    @Override
    public void logout(String token) {
        AuthContextUtil.set(null);
        redisTemplate.delete(ConstantConfig.redis_login_token_prefix + token);
    }

    @Override
    public List<User> getList(User user) {
        List<User> list=baseMapper.selectList(QueryWraperUtil.getQueryWrap(user));
        list.forEach(NormalUtil::userInfoDesensitizedHandle);
        return list;
    }

    @Override
    public PageVo<User> getPage(PageDto<User> pageDto) {
        IPage<User> iPage=baseMapper.selectPage(new Page<>(pageDto.getCurrent(),pageDto.getSize()),QueryWraperUtil.getQueryWrap(pageDto.getCdt()));
        List<User> list=iPage.getRecords();
        list.forEach(NormalUtil::userInfoDesensitizedHandle);
        return new PageVo<>(list,iPage.getCurrent(),iPage.getSize(),iPage.getTotal());
    }

    @Override
    public LoginVo loginByWxScan(String code,HttpServletRequest request) {
        String reqUrl="https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code";
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        Map<String, Object> body = new HashMap<>();
        body.put("appid", ConstantConfig.webAppid);
        body.put("secret", ConstantConfig.webSecret);
        body.put("code", code);
        try{
            body.put("redirect_uri", URLEncoder.encode(ConstantConfig.webUrl, StandardCharsets.UTF_8.toString()));
        }catch (Exception e){
            e.printStackTrace();
        }
        HttpResponse httpResponse=HttpRequest.post(reqUrl).headerMap(headers,true).form(body).execute();
        int res_status=httpResponse.getStatus();
        String res_body=httpResponse.body();
        if(res_status>=200 && res_status<300) {
            JSONObject resBodyJson = JSON.parseObject(res_body);
//          {access_token,expires_in,refresh_token,openid,scope,}=res
            String openid=(String)resBodyJson.get("openid");
            QueryWrapper<User> queryWrapper=new QueryWrapper<>();
            queryWrapper.eq("wx_openId",openid);
            User user = baseMapper.selectOne(queryWrapper);
            if(user==null) {
                // 为null则当前微信未绑定系统用户，此时请让用户通过短信验证码登录的方式绑定系统用户，wxBindToUserAndLogin
                LoginVo loginVo=new LoginVo();
                String bindKey=IdUtil.fastUUID();
                loginVo.setWxBindKey(bindKey);
                ValueOperations<String,String> redisOps = redisTemplate.opsForValue();
                redisOps.set(ConstantConfig.redis_wxBindKey_prefix + bindKey,openid,30,TimeUnit.MINUTES);
                return  loginVo;
            }
            else return new LoginVo(createToken(user,request),user.getId().toString()+"666",null);
        }else {
          throw new CustomException(201,"扫码授权失败："+res_body);
        }
    }

    @Override
    public LoginVo wxBindToUserAndLogin(LoginDto loginDto, HttpServletRequest request) {
        ValueOperations<String,String> redisOps = redisTemplate.opsForValue();
        String openid=redisOps.get(ConstantConfig.redis_wxBindKey_prefix+loginDto.getWxBindKey());
        if(openid==null) throw new CustomException(StatusCodeEnum.wxBindKeyOutDate);
        return  loginByPhone(loginDto,openid,request);
    }

    @Override
    @Transactional(rollbackFor = CustomException.class)    // 当抛出自定义异常时，会回到初始状态。这里就是删除或添加失败就都不执行
    public Boolean giveRoles(GiveRolesDto giveRolesDto) {
        List<UserRole> list=new ArrayList<>();
        Integer delNum;
        boolean saveSuccess;
        giveRolesDto.getRoleIds().forEach(v->{ list.add(new UserRole(giveRolesDto.getUserId(),v)); });
        try{
            delNum=userRoleService.deleteByUserId(giveRolesDto.getUserId());
            saveSuccess=userRoleService.saveBatch(list);
        }catch(Exception e){
            throw new CustomException(StatusCodeEnum.give_fail);
        }
        return delNum>0 && saveSuccess;
    }

    @Override
    public List<Role> getRolesInfo(Long uid) {
        return baseMapper.getRolesInfoByUserId(uid);
    }

    @Override
    public List<Menu> getMenusInfo(Long uid) {
        return baseMapper.getMenusInfoByUserId(uid);
    }


    public String createToken(User user,HttpServletRequest request){
        if(user.getStatus().equals(0)) throw new CustomException(StatusCodeEnum.user_baned);
        StringBuilder token = new StringBuilder();
        for (int i = 0; i < 3; i++) {
            token.append(IdUtil.simpleUUID());
        }
        List<Menu> menus=null;
        Map<String,String> menuMap=new LinkedHashMap<>();
        try {
            menus=getMenusInfo(user.getId());
        }catch (Exception e){
            StaticLog.warn(user.getUsername()+"登录时获取菜单失败");
        }
        if(menus!=null){
            menus.forEach(v->{ menuMap.put(v.getName(),JSON.toJSONString(v)); });
        }
        AuthContextUtil.IUser iUser =new AuthContextUtil.IUser();
        BeanUtil.fillBeanWithMap(BeanUtil.beanToMap(user),iUser,true);
        iUser.setIp(NormalUtil.getRequestIp(request));
        iUser.setMenu(menus);
        iUser.setNameToMenuMap(menuMap);
        Date d=new Date();
        try{
            user.setLastLoginTime(d);
            baseMapper.updateById(user);
            iUser.setLastLoginTime(d);
        }catch (Exception e){
            StaticLog.warn(user.getUsername()+"登录时设置最后登录时间失败");
        }
        System.out.println("login："+iUser.getIp());
        ValueOperations<String,String> redisOps = redisTemplate.opsForValue();
        redisOps.set(ConstantConfig.redis_login_token_prefix + token, JSON.toJSONString(iUser),ConstantConfig.token_expire, ConstantConfig.token_expire_unit);
        return token.toString();
    }
}




